package org.vacoor.xqq.core.mod.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vacoor.xqq.core.bean.AuthcState;
import org.vacoor.xqq.core.bean.ChatToken;
import org.vacoor.xqq.core.bean.Contact;
import org.vacoor.xqq.core.bean.Status;
import org.vacoor.xqq.core.http.HttpRequestor;
import org.vacoor.xqq.core.http.Request;
import org.vacoor.xqq.core.http.Response;
import org.vacoor.xqq.core.mod.Authenticator;
import org.vacoor.xqq.core.util.Jacksons;
import org.vacoor.xqq.core.util.QQUtil;
import org.vacoor.xqq.core.util.Regexs;

import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * User: vacoor
 * Date: 10/21/13
 * Time: 10:00 AM
 */
public class SimpleAuthenticator implements Authenticator {
    private static final Logger logger = LoggerFactory.getLogger(SimpleAuthenticator.class);

    // APPID SmartQQ = 501004106,WebQQ = "1003903"
    public static final String APPID = "501004106";
//    public static final String APPID = "nLogin3gqq";

    // 回调URL WebQQ 为: http://web2.qq.com/loginproxy.html
    public static final String LOGIN_PROXY_URL = "http://w.qq.com/proxy.html";

    // 登录指纹可以在这个html中找到 https://ui.ptlogin2.qq.com/cgi-bin/login?daid=164&target=self&richStyle=5&mibao_css=m_webqq&appid=1003903&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fweb2.qq.com%2Floginproxy.html&f_url=loginerroralert&strong_login=1&login_state=10&ui=20130723001
    public static final String LOGIN_SIG = "EBR2alDCp77NL6ObqgBmE-WPxgEky2oTueEcVlc1g-rNroRUFfoB7CI4*YWN2-Dy";

    // 账户安全性校验(包括加密密钥的获取)
    public static final String CHECK_URL = "http://check.ptlogin2.qq.com/check";

    // 验证码获取
    public static final String CAPTCHA_URL = "http://captcha.qq.com/getimage";

    // 登录URL
    public static final String LOGIN_URL = "http://ptlogin2.qq.com/login";

    // Smart QQ 获取 vfwebqq URL, 使用login2返回的vfwebqq也可以
    public static final String GET_VFWEBQQ_URL = "http://s.web2.qq.com/api/getvfwebqq";

    public static final String LOGIN2_URL = "http://d.web2.qq.com/channel/login2";

    // ------------------------------------- //

    private String account; // 登录账户
    private String passwd;  // 明文密码
    private Status state;

    private byte[] key;     // 加密密钥
    private String verify;  // 验证码

    public SimpleAuthenticator(String account, String passwd, Status state) {
        this.account = account.toLowerCase(); // 不允许大写
        this.passwd = passwd;
        this.state = state;
    }

    /**
     * 安全性检查
     * 该方法校验是否需要输入验证码, 并接受用于加密密码的密钥
     * 响应有以下两种
     * 1. ptui_checkVC('0','!MII','\x00\x00\x00\x00\x83\xec\xb6\x7c');
     * 直接使用 '!MII' 作为验证码, 使用密钥'\x00\x00\x00\x00\x83\xec\xb6\x7c'加密
     * <p/>
     * 2. ptui_checkVC('1','80012das923jjsdfas3asds8891','\x00\x00\x00\x00\x83\xec\xb6\x7c');
     * 需要获取验证码后, 使用密钥进行加密
     *
     * @return 是否需要获取验证码
     */
    @Override
    public boolean safetyCheck() {
        boolean needCheck = true;
        logger.info("发送校验信息..");

        Future<Response> f = HttpRequestor.getInstance().send(
                new Request(CHECK_URL, Request.HttpMethod.GET)
                        .addParameter("uin", this.account)
                        .addParameter("appid", APPID)
                        .addParameter("login_sig", LOGIN_SIG)
                        .addParameter("u1", LOGIN_PROXY_URL + "?login2qq=1&webqq_type=" + state.getCode()) //会自动转码不要转, 否则http client 2次转吗
                        .addParameter("r", Math.random()),
                null
        );

        try {
            Response resp = f.get();
            String s = resp.getContent().asString();
            String[][] grep = Regexs.grep(s, "ptui_checkVC\\(\'([0-9])','([^']*)','([^']{32})'\\)");
            // 正确解析结果
            if (1 == grep.length) {
                logger.debug("解析校验信息: {}", s);
                SimpleAuthenticator.this.key = QQUtil.parseKey(grep[0][3]);

                if ("0".equals(grep[0][1])) {
                    SimpleAuthenticator.this.verify = grep[0][2];
                    needCheck = false;
                }

            }
        } catch (Throwable e) {
            logger.error("解析校验信息出错.. ", e);
            System.exit(1);
        }
        return needCheck;
    }

    /**
     * 获取验证码
     *
     * @return 验证码字节
     */
    @Override
    public byte[] getCaptcha() {
        logger.info("获取验证码..");
        try {
            Response response = HttpRequestor.getInstance().send(
                    new Request(CAPTCHA_URL, Request.HttpMethod.GET)
                            .addParameter("aid", APPID)
                            .addParameter("uin", this.account)
                            .addParameter("r", "" + Math.random()),
                    null
            ).get();
            return response.getContent().asBytes();
        } catch (Throwable e) {
            logger.error("获取验证码出错..");
            System.exit(1);
        }
        return new byte[0];
    }

    /**
     * 认证
     * status, ?, url, ?, onMsg, 'nickname/uin'
     * ptuiCB('0','0','http://ptlogin4.web2.qq.com/check_sig?pttype=1&uin=2214963100&service=login&nodirect=0&ptsig=cObkasAuS3HyGCUstbYfDAfe6QAGiAPqSt7cd3rGOao_&s_url=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&f_url=&ptlang=2052&ptredirect=101&aid=501004106&daid=164&j_later=0&low_login_hour=0&regmaster=0','1','登录成功！', '王测横');
     * ptuiCB('3','0','','0','您输入的帐号或密码不正确，请重新输入。', '2214963100');
     * ptuiCB('4','0','','0','您输入的验证码不正确，请重新输入。', '2214963100');
     * ptuiCB('7','0','','0','很遗憾，网络连接出现异常，请您稍后再试。(3372221109)', '2214963100');
     *
     * @return
     */
    @Override
    public AuthcState doAuthc() {
        logger.info("发送认证信息..");
        boolean success = false;
        try {
            String pass = QQUtil.encrypt(this.passwd, this.key, this.verify);
            Response response = HttpRequestor.getInstance().send(
                    new Request(LOGIN_URL, Request.HttpMethod.GET)
                            .addParameter("u", this.account)
                            .addParameter("p", pass)
                            .addParameter("verifycode", this.verify)
                            .addParameter("webqq_type", state.getCode() + "")
                            .addParameter("remember_uin", "1")
                            .addParameter("login2qq", "1")
                            .addParameter("aid", APPID)
                            .addParameter("h", "1")
                            .addParameter("ptredirect", "1")
                            .addParameter("ptlang", "2052")
                            .addParameter("daid", "164")
                            .addParameter("from_ui", "1")
                            .addParameter("pttype", "1")
                            .addParameter("u1", LOGIN_PROXY_URL + "?login2qq=1&webqq_type=" + state.getCode()) //会自动转码不要转, 否则http client 2次转吗
                            .addParameter("dumy", "")
                            .addParameter("fp", "loginerroralert")
                            .addParameter("action", "0-157-3220146")
                            .addParameter("mibao_css", "m_webqq")
                            .addParameter("ui", "1")
                            .addParameter("g", "1") //必须
                            .addParameter("login_sig", LOGIN_SIG),
                    null
            ).get();
            String s = response.getContent().asString();
            //        status, ?, url, ?, onMsg, 'nickname/uin'
            logger.debug("认证响应: {}", s);
            String[][] grep = Regexs.grep(s, "ptuiCB\\(\\s*'([0-9])'\\s*,\\s*'([0-9])'\\s*,\\s*'([^']*)'\\s*,\\s*'([0-9])'\\s*,\\s*'([^']*)'\\s*,\\s*'([^']*)'\\s*\\)");
            // 解析成功
            if (1 == grep.length) {
                String code = grep[0][1];
                String url = grep[0][3];
//                String onMsg = grep[0][5];
//                String nick = grep[0][6];

                AuthcState authcState = AuthcState.fromCode(Integer.parseInt(code));

                if(AuthcState.SUCCESS == authcState) {
                    sigCheck(url);
                }

                logger.info("认证消息: {}", authcState.getMsg());
                return authcState;
            } else {
                logger.warn("无法解析响应: {}", s);
            }
        } catch (Throwable e) {
            logger.warn("认证异常: {}", e);
        }
        return AuthcState.UNKNOWN_ERROR;
    }

    /**
     * 该请求主要是为了获取所需cookie
     *
     * @param sigCheckUrl
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    private void sigCheck(String sigCheckUrl) throws ExecutionException, InterruptedException {
        logger.info("校验认证指纹: {}", sigCheckUrl);
        Future<Response> checkSig = HttpRequestor.getInstance().send(new Request(sigCheckUrl, Request.HttpMethod.GET), null);
        checkSig.get();
        /*-
            checksig 完成后会进一步重定向以下URL来实现 cookie 跨域, 但是貌似这里不搞也没问题
            "http://w.qq.com/proxy.html?login2qq=1&webqq_type=40"(用于复制web2 cookie)
            在该URL中会创建 iframe src="http://web2.qq.com/web2_cookie_proxy.html"(提供web2 cookie)
        */
    }

    /**
     * 本来需要在访问 channel/login2 前调用, 获取vfwebqq, 但是发现直接使用 login2 vfwebqq 也没问题
     */
    /*
    @Deprecated
    public String getVfwebqq(String ptwebqq, String clientId) throws ExecutionException, InterruptedException, IOException {
        return HttpRequestor.getInstance().send(
                new Request(GET_VFWEBQQ_URL, Request.HttpMethod.GET)
                        .addParameter("ptwebqq", ptwebqq)
                        .addParameter("clientid", clientId)
                        .addParameter("psessionid", "")
                        .addParameter("ui", new Date().getTime() + "")
                                // 必须 Referer
                        .addHeader("Referer", "http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1"),
                null
        ).get().getMsgBody().asString();
    }
    */

    /**
     * 获取聊天Token
     *
     * @return
     */
    @Override
    public ChatToken getChatToken() {
        logger.info("获取Token ...");
        long clientId = QQUtil.genClientID();
        String ptwebqq = HttpRequestor.getInstance().getCookieValue("ptwebqq");
        try {
            String s = login2(ptwebqq, clientId);
            String[][] l2resp = Regexs.grep(s, "\"uin\":([0-9]+),.*\"vfwebqq\":\"([0-9a-zA-Z]+)\",.*\"psessionid\":\"([0-9a-z]+)\"");

            return new ChatToken(
                    Long.parseLong(l2resp[0][1]),   // uin
                    clientId,
                    ptwebqq,
                    l2resp[0][2],   // vfwebqq
                    l2resp[0][3]    // psessionid
            );
        } catch (Exception e) {
            logger.warn("获取Token失败: {}", e);
        }
        return null;
    }

    private String login2(String ptwebqq, long clientId) throws ExecutionException, InterruptedException, IOException {
        String jsonParam = Jacksons.createObjectNode()
                .put("ptwebqq", ptwebqq)
                .put("clientid", clientId)
                .put("status", state.getName())
                .toString();

        return HttpRequestor.getInstance().send(
                new Request(LOGIN2_URL, Request.HttpMethod.POST)
                        .addHeader("MsgBody-Type", "application/x-www-form-urlencoded")
                        .addHeader("Referer", "http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1")
                        .addParameter("r", jsonParam),
                null
        ).get().getContent().asString();
    }

    @Override
    public void setVerify(String verify) {
        this.verify = verify;
    }

    public String getVerify() {
        return verify;
    }
}
